﻿/*******************************************************************
 *     JameE
 *******************************************************************
  Copyright 2010 Clifford Champion and JameE contributors.
  
  This file is part of JameE.
  
  JameE is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.
  
  JameE is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
  
  You should have received a copy of the GNU General Public License
  along with JameE.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************/
 
/// <reference path="../Base/Classer.js" />
/// <reference path="../Base/IncludeLoader.js" />
/// <reference path="Aabb.js" />

$include(
[
	"Engine/Aabb.js"
])

$class(
"JameE.Engine.BroadPhase$ObjectEntry",
{
	iId: 0,
	oAabb: null,
	oGameObject: null
});

$class(
"JameE.Engine.BroadPhase$OverlapEntry",
{
	oGameObjectA: null,
	oGameObjectB: null
});

$class(
"JameE.Engine.BroadPhase$SingleAxisObjectEntry",
{
	fMin: 0,
	fMax: 0,
	iId: 0
});

$class(
"JameE.Engine.BroadPhase$SingleAxisOverlapEntry",
{
	iKey: 0, // indexA << 16 | indexB
	getIdA: function () { return this.iKey >> 16; },
	getIdB: function () { return this.iKey & 0x0000FFFF; }
});

$class(
"JameE.Engine.BroadPhase",
{

	/////////////////////
	// FIELDS
	/////////////
	_arr_oObjects: null, // BroadPhase$ObjectEntry[]
	_arr_oOverlaps: null, //BroadPhase$OverlapEntry[]

	_arr_oSortedObjectsX: null,
	_arr_oSortedObjectsY: null,

	_hash_oOverlapsX: null,
	_hash_oOverlapsY: null,

	_arr_oOverlaps: null,


	/////////////////////
	// CONSTRUCTORS
	/////////////
	_ctor: function () {
		this._arr_oObjects = [];
		this._arr_oOverlaps = [];

		this._arr_oSortedObjectsX = [];
		this._arr_oSortedObjectsY = [];
		this._hash_oOverlapsX = {};
		this._hash_oOverlapsY = {};
	},


	/////////////////////
	// METHODS
	/////////////
	add: function (arr_oSceneObjects) {
		/// <summary>Adds a single object or batch of objects to the world.</summary>

		if ($typeof(arr_oSceneObjects) !== "array") {
			arr_oSceneObjects = [arr_oSceneObjects];
		}

		for (var i = arr_oSceneObjects.length - 1; i >= 0; --i) {
			var oSceneObject = arr_oSceneObjects[i];

			//////
			// add to basic list
			var oObjectEntry = new JameE.Engine.BroadPhase$ObjectEntry();
			var oAabb = oSceneObject.getAabb();
			var iObjectId = this._arr_oObjects.length;
			oObjectEntry.oAabb = oAabb;
			oObjectEntry.oGameObject = oSceneObject;
			oObjectEntry.iId = iObjectId;
			this._arr_oObjects[iObjectId] = oObjectEntry;


			//////
			// add to x-axis list
			var fMinX = oAabb.fXMin;
			var fMaxX = oAabb.fXMax;

			// create new entry
			var oEntry = new JameE.Engine.BroadPhase$SingleAxisObjectEntry();
			oEntry.fMin = fMinX;
			oEntry.fMax = fMaxX;
			oEntry.iId = iObjectId;

			// search
			var arr_oObjects = this._arr_oSortedObjectsX;
			var iArrLength = arr_oObjects.length;
			if (iArrLength === 0) {
				arr_oObjects.push(oEntry);
			}
			else if (iArrLength !== 0 && arr_oObjects[0].fMin >= fMinX) {
				arr_oObjects.splice(0, 0, oEntry);
			}
			else {
				var bAdded = false;
				for (var i = 0; i < iArrLength; ++i) {
					var oEntry2 = arr_oObjects[i];
					if (oEntry2.fMin <= fMinX) {
						// insert entry
						arr_oObjects.splice(i + 1, 0, oEntry);
						bAdded = true;
						break;
					}
				}
				if (!bAdded) {
					arr_oObjects.push(oEntry);
				}
			}

			//////
			// add to y-axis list
			var fMinY = oAabb.fYMin;
			var fMaxY = oAabb.fYMax;
			oEntry = new JameE.Engine.BroadPhase$SingleAxisObjectEntry();
			oEntry.fMin = fMinY;
			oEntry.fMax = fMaxY;
			oEntry.iId = iObjectId;

			// search
			arr_oObjects = this._arr_oSortedObjectsY;
			if (iArrLength === 0) {
				arr_oObjects.push(oEntry);
			}
			else if (iArrLength !== 0 && arr_oObjects[0].fMin >= fMinY) {
				arr_oObjects.splice(0, 0, oEntry);
			}
			else {
				bAdded = false;
				for (var i = 0; i < iArrLength; ++i) {
					var oEntry2 = arr_oObjects[i];
					if (oEntry2.fMin < fMinY) {
						// insert entry
						arr_oObjects.splice(i + 1, 0, oEntry);
						bAdded = true;
						break;
					}
				}
				if (!bAdded) {
					arr_oObjects.push(oEntry);
				}
			}
		}


		// update overlaps
		this._updateOverlaps();
	},

	remove: function (oGameObject) {
		var iArrLength;

		//////
		// locate and remove from unsorted list
		var iId = -1;
		iArrLength = this._arr_oObjects.length;
		for (var i = 0; i < iArrLength; ++i) {
			var oEntry = this._arr_oObjects[i];
			if (oEntry === null) {
				continue;
			}
			if (oEntry.oGameObject === oGameObject) {
				iId = i;
				this._arr_oObjects[i] = null;
				break;
			}
		}


		//////
		// remove from x-axis list
		var arr_oObjects = this._arr_oSortedObjectsX;
		iArrLength = arr_oObjects.length;
		for (var i = 0; i < iArrLength; ++i) {
			var oEntry = arr_oObjects[i];
			if (oEntry.iId === iId) {
				arr_oObjects.splice(i, 1);
				break;
			}
		}


		//////
		// remove from y-axis list
		arr_oObjects = this._arr_oSortedObjectsY;
		for (var i = 0; i < iArrLength; ++i) {
			var oEntry = arr_oObjects[i];
			if (oEntry.iId === iId) {
				arr_oObjects.splice(i, 1);
				break;
			}
		}
	},

	getAabbOverlaps: function () {
		return this._arr_oOverlaps;
	},

	castPoint: function (oPoint) {
		/// <summary>Test for intersections with the given point.</summary>

		$require_type(oPoint, "Math.Vector3");

		var fX = oPoint.fX;
		var fY = oPoint.fY;


		var arr_oSortedX = this._arr_oSortedObjectsX;
		var iSortedXLength = arr_oSortedX.length;
		var oHitsX = {};
		var bHitsXFound = false;
		for (var i = 0; i < iSortedXLength; ++i) {
			var oEntry = arr_oSortedX[i];
			if (oEntry.fMin > fX) {
				break;
			}
			if (oEntry.fMax > fX) {
				oHitsX[oEntry.iId] = oEntry;
				bHitsXFound = true;
			}
		}
		if (!bHitsXFound) {
			return [];
		}

		var arr_oSortedY = this._arr_oSortedObjectsY;
		var iSortedYLength = arr_oSortedY.length;
		var oHits = [];
		for (var i = 0; i < iSortedYLength; ++i) {
			var oEntry = arr_oSortedY[i];
			if (oEntry.fMin > fY) {
				break;
			}
			if (oEntry.fMax > fY) {
				// a hit in the y. is there also a hit in the x?
				if (oHitsX[oEntry.iId] != null) {
					oHits.push(oEntry.iId);
				}
			}
		}

		var oHitsGameObjects = [];
		for (var k in oHits) {
			var oEntry = this._arr_oObjects[oHits[k]];
			oHitsGameObjects.push(oEntry.oGameObject);
		}
		return oHitsGameObjects;
	},


	/////////////////////
	// METHODS: PRIVATE
	/////////////
	_updateOverlaps: function () {

		// update x-axis overlaps
		this._updateOverlapsSpecificAxis(
			this._arr_oSortedObjectsX,
			this._hash_oOverlapsX
		);

		// update y-asix overlaps
		this._updateOverlapsSpecificAxis(
			this._arr_oSortedObjectsY,
			this._hash_oOverlapsY
		);

		// update complete overlaps list by finding common entries in both sets
		this._arr_oOverlaps.length = 0;
		for (var iKey in this._hash_oOverlapsX) {
			if (this._hash_oOverlapsY[iKey]) {

				var oSingleAxisEntry = this._hash_oOverlapsX[iKey];
				var oOverlap = new JameE.Engine.BroadPhase$OverlapEntry();
				oOverlap.oGameObjectA = this._arr_oObjects[oSingleAxisEntry.getIdA()];
				oOverlap.oGameObjectB = this._arr_oObjects[oSingleAxisEntry.getIdB()];
				this._arr_oOverlaps.push(oOverlap);
			}
		}
	},

	_updateOverlapsSpecificAxis: function (arr_oEntries, hash_oOverlaps) {
		if (arr_oEntries.length <= 1) {
			for (var strKey in hash_oOverlaps) {
				delete hash_oOverlaps[strKey];
			}
			return;
		}

		var arr_iEnteredObjectIndices = [];
		arr_iEnteredObjectIndices.push(0);
		var fPos = arr_oEntries[0].fMin;


		var iLength = arr_oEntries.length;
		for (var i = 1; i < iLength; ++i) {

			// set new position
			fPos = arr_oEntries[i].fMin;

			// has the new position left any objects?
			for (var j = 0; j < arr_iEnteredObjectIndices.length; ++j) {
				if (fPos > arr_oEntries[arr_iEnteredObjectIndices[j]].fMax) {
					arr_iEnteredObjectIndices.splice(j, 1);
					--j;
				}
			}

			// whatever we're still inside, we're now overlapping with
			for (var j = 0; j < arr_iEnteredObjectIndices.length; ++j) {

				// create an overlap entry
				var oOverlapEntry = new JameE.Engine.BroadPhase$SingleAxisOverlapEntry();
				var iIdA = arr_oEntries[i].iId;
				var iIdB = arr_oEntries[arr_iEnteredObjectIndices[j]].iId
				var iOverlapKey = (iIdA << 16) | iIdB;
				oOverlapEntry.iKey = iOverlapKey;

				// add to overlap list
				hash_oOverlaps[iOverlapKey] = oOverlapEntry;
			}

			// add the current object to the entered objects list
			arr_iEnteredObjectIndices.push(i);
		}
	}
});

$include_notify("Engine/BroadPhase.js");